package de.argh.jteas.mojo;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

import de.argh.jteas.mojo.controller.TemplateController;
import de.argh.jteas.mojo.controller.TypeCollector;
import de.argh.jteas.mojo.exception.MappingNotFoundException;
import de.argh.jteas.mojo.model.settings.Settings;

/**
 * Goal which touches a timestamp file.
 * 
 * @goal generate
 * 
 * @phase generate-sources
 */
public class Jteas extends AbstractMojo {
	
	/**
	 * Location of the output directory for the ActionScript classes.
	 * 
	 * @parameter expression="${project.build.directory}/generated/as"
	 * @required
	 */
	private static File asDirectory;

	/**
	 * Location of the JAR file.
	 * 
	 * @parameter expression=""
	 * @required
	 */
	private static File jarFile;

	/**
	 * Base package which all object factories will share. An Interface for enumerations in ActionScript style will be
	 * created in this package.
	 * 
	 * @parameter expression=""
	 * @required
	 */
	private static String basePackage;

	/**
	 * List of object-factories from JAXB.
	 * 
	 * @parameter expression=""
	 * @required
	 */
	private static String[] objectFactories;

	/**
	 * Decides if Enums will translated as String or as it's itself
	 * 
	 * @parameter expression=false
	 * @required
	 */
	private static boolean stringForEnum;


	/**
	 * Decides if annotations FlexXB format should be created
	 * 
	 * @parameter expression=true
	 * @required
	 */
	private static boolean createAnnotations;

	/**
	 * Shall fields be ordered
	 * 
	 * @parameter expression=true
	 * @required
	 */
	private static boolean orderedFields;

	/**
	 * ASClassAnnotation properties for translation in flexxb supported ActionScript Classes
	 * 
	 * @parameter
	 */
	private static Properties xmlAnnotations;

	/**
	 * ASClassAnnotation version
	 * 
	 * @parameter
	 */
	private static String xmlAnnotationVersion;
	
	/**
	 * name: Packagename<br>
	 * value: prefix:uri
	 * 
	 * @parameter
	 */
	private static Properties uriPackageProperties;
   
	private static Settings initSettings() {

		Settings settings = new Settings();

		settings.setAsDirectory(asDirectory);
		settings.setBasePackage(basePackage);
		settings.setObjectFactories(objectFactories);
		settings.setStringForEnum(stringForEnum);
		settings.setCreateAnnotations(createAnnotations);
		settings.setOrderedFields(orderedFields);
		settings.setXmlAnnotations(xmlAnnotations);
		settings.setXmlAnnotationVersion(xmlAnnotationVersion);
		settings.setUriPackageProperties(uriPackageProperties);

		return settings;
	}
	
	public void execute() throws MojoExecutionException {
		try {
			
			Settings settings = initSettings();
			
			if (!jarFile.exists()) {
				throw new MojoExecutionException("jarFile not found");
			}
			
			logConfiguration();
						
			addJarsToClassPath(Thread.currentThread().getContextClassLoader(), new File[] {jarFile});
			getLog().info(jarFile.toURI().toURL().toString() + " loaded.");
		
			List<Class<?>> objFactories = listObjectFactoryClasses(settings);
		
			TypeCollector typeCollector = new TypeCollector(settings, objFactories);
			typeCollector.parseThroughObjectFactories(basePackage);
			
			TemplateController tempController = new TemplateController(settings);
			tempController.generateSourceForClassesWith(typeCollector.getAsClasses(), "ASClass.vm");
			
		} catch (ClassNotFoundException e) {
			throw new MojoExecutionException("ObjectFactory not found.", e);

		} catch (MalformedURLException e) {
			throw new MojoExecutionException("Malformed URL. See stacktrace... ", e);
			
		} catch (MappingNotFoundException e) {
			throw new MojoExecutionException("Mapping", e);
			
		} catch (Exception e) {
			throw new MojoExecutionException("Template generation", e);
			
		}
		
	}
	
	private List<Class<?>> listObjectFactoryClasses(Settings settings) throws ClassNotFoundException {
		List<Class<?>> objFactories = new ArrayList<Class<?>>();
		for (String factory : settings.getObjectFactories()) {
			Class<?> clazz = Class.forName(factory.trim());
			objFactories.add(clazz);
			getLog().info("Methods in ObjectFactory: " + factory + " => " + String.valueOf(clazz.getDeclaredMethods().length));
		}
		return objFactories;
	}

	private void logConfiguration() throws MojoExecutionException {
		getLog().info(asDirectory.toString());
		getLog().info(jarFile.toString());
		getLog().info(basePackage);
		for (String str : objectFactories) {
			getLog().info(str);
		}
		
		getLog().info("createAnnotation: " + (createAnnotations == true ? "true" : "false"));
		if (createAnnotations) {
			getLog().info(xmlAnnotations.getProperty("xmlClass") == null ? "null" : xmlAnnotations.getProperty("xmlClass"));
			getLog().info(xmlAnnotations.getProperty("xmlAttribute") == null ? "null" : xmlAnnotations.getProperty("xmlAttribute"));
			getLog().info(xmlAnnotations.getProperty("xmlElement") == null ? "null" : xmlAnnotations.getProperty("xmlElement"));
			getLog().info(xmlAnnotations.getProperty("xmlArray") == null ? "null" : xmlAnnotations.getProperty("xmlArray"));
			
			if (xmlAnnotationVersion != null) {
				getLog().info("xmlVersion: " + xmlAnnotationVersion);
			} else {
				throw new MojoExecutionException("xmlVersion must not be null, if XML Annotations should be created");
			}
		}
		
	}

	private static void addJarsToClassPath(ClassLoader classLoader, File[] jars) {
		if (classLoader instanceof URLClassLoader) {
			try {
				Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
				addUrlMethod.setAccessible(true);
				if (null != addUrlMethod) {
					for (File jar : jars) {
						try {
							addUrlMethod.invoke(classLoader, jar.toURI().toURL());
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

		}
	}

}
